
#include "debug.h"
#include "video.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <time.h>

#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/select.h>

#include <errno.h>

#include <linux/videodev2.h>

static int video_free_bufdup(struct camera_class *cam, struct camera_buf **buf_tmp)
{
	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	if(NULL != buf_tmp && NULL != *buf_tmp && MAP_FAILED != (*buf_tmp)->start && NULL != (*buf_tmp)->start)
	{
		free((*buf_tmp)->start);

		(*buf_tmp)->start = NULL;
		(*buf_tmp)->length = 0;
		(*buf_tmp)->id = 0xFFFF;
		(*buf_tmp)->arg = NULL;
		
		free(*buf_tmp);
		*buf_tmp = NULL;

		return 0;
	}
	else
	{
		return -1;
	}
}

static int video_get_bufdup(struct camera_class *cam, struct camera_buf **buf_tmp)
{
	int ret = 0;
	uint32_t currert_index = 0;

	if(NULL == cam || NULL == buf_tmp || NULL != *buf_tmp)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	if(0 != cam->living)
	{
		errno = EINVAL;
		return -EINVAL;
	}
	
	*buf_tmp = (struct camera_buf *)calloc(1, sizeof(**buf_tmp));
	
	if(NULL == *buf_tmp)
	{
		errno = ENOMEM;
		return -ENOMEM;
	}

	currert_index = cam->active_buf_index;

	(*buf_tmp)->start = (unsigned char *)calloc(1, cam->buf[currert_index].length);

	if(NULL != (*buf_tmp)->start)
	{
		(*buf_tmp)->length = cam->buf[currert_index].length;
		(*buf_tmp)->id = currert_index;
		(*buf_tmp)->arg = cam;

		memcpy((*buf_tmp)->start, cam->buf[currert_index].start, (*buf_tmp)->length);

		ret = 0;
	}
	else
	{
		ret = -1;
	}

	return ret;
}

static int video_get_frame(struct camera_class *cam, int timeout)
{
	int ret = 0;

	struct timeval tv;
	fd_set fds;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	if(0 != cam->living)
	{
		errno = EINVAL;
		return -EINVAL;
	}

	FD_ZERO(&fds);
	FD_SET(cam->fd, &fds);

	memset(&tv, 0, sizeof(tv));

	tv.tv_sec = timeout;
	tv.tv_usec = 0;

	while(1)
	{
		ret = select(cam->fd + 1, &fds, NULL, NULL, &tv);
	
		if(0 > ret)
		{
			return -1;
		}
		else if(0 == ret)
		{
			return -1;
		}
		else
		{
			return 0;
		}

		break;
	}

	return -1;
}

static int video_ignore_frame(struct camera_class *cam, int timeout, int n_count)
{
	int i = 0;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	if(0 != cam->living)
	{
		errno = EINVAL;
		return -EINVAL;
	}

	for(i = 0; n_count > i; i++)
	{
		if(0 != cam->get_frame(cam, 3))
		{
			return -1;
		}
		if(0 != cam->pop_buf(cam))
		{
			return -1;
		}
		if(0 != cam->push_buf(cam))
		{
			return -1;
		}
	}

	return 0;
}

static int video_stream_living(struct camera_class *cam)
{
	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	return 0 == cam->living ? 0 : -1;
}

static int video_stream_stop(struct camera_class *cam)
{
	int ret = 0;

	enum v4l2_buf_type type;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = ioctl(cam->fd, VIDIOC_STREAMOFF, &type);

	if(0 != ret)
	{
		return ret;
	}

	cam->living = -1;

	return 0;
}

static int video_stream_start(struct camera_class *cam)
{
	int ret = 0;

	enum v4l2_buf_type type;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = ioctl(cam->fd, VIDIOC_STREAMON, &type);

	if(0 != ret)
	{
		return ret;
	}

	cam->living = 0;

	return 0;
}

static int video_pop_buf(struct camera_class *cam)
{
	int ret = 0;

	struct v4l2_buffer buf;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	memset(&buf, 0, sizeof(buf));

	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;

	ret = ioctl(cam->fd, VIDIOC_DQBUF, &buf);

	if(0 != ret)
	{
		return ret;
	}

	cam->active_buf_index = buf.index;

	return 0;
}

static int video_push_buf(struct camera_class *cam)
{
	int ret = 0;

	struct v4l2_buffer buf;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	memset(&buf, 0, sizeof(buf));

	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;

	buf.index = cam->active_buf_index;

	ret = ioctl(cam->fd, VIDIOC_QBUF, &buf);

	if(0 != ret)
	{
		return ret;
	}

	return 0;
}

static int video_init_buf(struct camera_class *cam)
{
	int ret = 0;

	uint32_t i = 0;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	for(i = 0; cam->n_buf > i; i++)
	{
		cam->active_buf_index = i;

		ret = cam->push_buf(cam);

		if(0 != ret)
		{
			return ret;
		}
	}

	cam->active_buf_index = -1;

	return 0;
}

static int video_mmap_buf(struct camera_class *cam)
{
	int ret = 0;

	uint32_t i = 0;
	struct v4l2_buffer buf;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	for(i = 0; cam->n_buf > i; i++)
	{
		memset(&buf, 0, sizeof(buf));

		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;

		ret = ioctl(cam->fd, VIDIOC_QUERYBUF, &buf);

		if(0 != ret)
		{
			return ret;
		}

		cam->buf[i].length = buf.length;
		cam->buf[i].start = mmap(NULL, buf.length, PROT_READ, MAP_SHARED, cam->fd, buf.m.offset);

		if(MAP_FAILED == cam->buf[i].start || NULL == cam->buf[i].start)
		{
			return -1;
		}
	}

	return 0;
}

static int video_alloc_buf(struct camera_class *cam)
{
	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}
	
	if(0 >= cam->n_buf)
	{
		errno = EINVAL;
		return -EINVAL;
	}

	cam->buf = (struct camera_buf *)calloc(cam->n_buf, sizeof(*(cam->buf)));

	if(NULL == cam->buf)
	{
		errno = ENOMEM;
		return -ENOMEM;
	}

	return 0;
}

static uint32_t video_req_buf(struct camera_class *cam, uint32_t num)
{
	int ret = 0;

	struct v4l2_requestbuffers req;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	if(64 < num)
	{
		errno = EINVAL;
		return -EINVAL;
	}

	memset(&req, 0, sizeof(req));

	req.count = num;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory = V4L2_MEMORY_MMAP;

	ret = ioctl(cam->fd, VIDIOC_REQBUFS, &req);

	if(0 != ret)
	{
		cam->n_buf = -1;
		return ret;
	}

	cam->n_buf = req.count;

	return req.count;
}

static int video_set_fmt(struct camera_class *cam)
{
	int ret = 0;

	struct v4l2_format fmt;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	//memset(&fmt, 0, sizeof(fmt));

	//fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	//ret = ioctl(cam->fd, VIDIOC_G_FMT, &fmt);

	/*if(0 != ret)
	{
		WAR_DUMP("VIDIOC_G_FMT FAILED");
		//return ret;
	}*/

	memset(&fmt, 0, sizeof(fmt));

	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	fmt.fmt.pix.width = cam->width;
	fmt.fmt.pix.height = cam->height;
	fmt.fmt.pix.pixelformat = cam->pixel_format;
	fmt.fmt.pix.field = V4L2_FIELD_ANY;

	ret = ioctl(cam->fd, VIDIOC_S_FMT, &fmt);

	if(0 != ret)
	{
		WAR_DUMP("VIDIOC_S_FMT FAILED");
		return ret;
	}

	return 0;
}

static int video_enum_fmt(struct camera_class *cam)
{
	struct v4l2_fmtdesc fmtdesc;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	memset(&fmtdesc, 0, sizeof(fmtdesc));

	fmtdesc.index = 0;
	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	printf("Support format:\n");
	while(0 == ioctl(cam->fd, VIDIOC_ENUM_FMT, &fmtdesc) && 0 != fmtdesc.pixelformat)
	{
		printf("[%d] = {\n\t.pixelformat = '%c%c%c%c',\n\t.description = '%s'\n}\n", fmtdesc.index, fmtdesc.pixelformat & 0xFF, (fmtdesc.pixelformat >> 8) & 0xFF, (fmtdesc.pixelformat >> 16) & 0xFF, (fmtdesc.pixelformat >> 24) & 0xFF, fmtdesc.description);
		fmtdesc.index++;
	}
	return 0;
}

static int video_show_cap(struct camera_class *cam)
{
	int ret = 0;

	struct v4l2_capability capability;

	char buf[1024] = {0};

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	memset(&capability, 0, sizeof(capability));

	ret = ioctl(cam->fd, VIDIOC_QUERYCAP, &capability);

	if(0 != ret)
	{
		return ret;
	}
	
	memset(buf, 0, sizeof(buf));
	
	snprintf(buf, sizeof(buf), "\n%-25s%s\n%-25s%s\n%-25s%s\n%-25s%x\n%-25s%s\n%-25s%s\n", "driver:", capability.driver, "card", capability.card, "bus_info", capability.bus_info, "capabilities", capability.capabilities, "V4L2_CAP_VIDEO_CAPTURE", capability.capabilities & V4L2_CAP_VIDEO_CAPTURE ? "OK" : "Not Support", "V4L2_CAP_STREAMING", capability.capabilities & V4L2_CAP_STREAMING ? "OK" : "Not Support");

	printf("%s\n", buf);

	return 0;
}

static int video_set_channel(struct camera_class *cam)
{
	int ret = 0;

	struct v4l2_input input;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	memset(&input, 0, sizeof(input));
	input.index = cam->channel;

	ret = ioctl(cam->fd, VIDIOC_ENUMINPUT, &input);
	
	if(0 != ret)
	{
		return ret;
	}

	ret = ioctl(cam->fd, VIDIOC_S_INPUT, &input);

	if(0 != ret)
	{
		return ret;
	}

	return 0;
}

static int video_set_new_channel(struct camera_class *cam, int new_channel)
{
	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	cam->channel = new_channel;
	return video_set_channel(cam);
}

static int video_is_v4l2_dev(struct camera_class *cam)
{
	struct stat st;

	memset(&st, 0, sizeof(st));

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}

	if(0 != access(cam->dev_name, F_OK))
	{
		errno = ENODEV;
		return -ENODEV;
	}

	if(0 != access(cam->dev_name, R_OK))
	{
		errno = EACCES;
		return -EACCES;
	}

	if(0 != access(cam->dev_name, W_OK))
	{
		errno = EACCES;
		return -EACCES;
	}

	if(0 != stat(cam->dev_name, &st))
	{
		errno = ENXIO;
		return -ENXIO;
	}

	if(S_IFCHR != (S_IFMT & st.st_mode))
	{
		errno = ENODEV;
		return -ENODEV;
	}

	return 0;
}

static int video_open(struct camera_class *cam)
{
	int ret = 0;

	if(NULL == cam)
	{
		errno = EFAULT;
		return -EFAULT;
	}
	
	printf("right\n");

	ret = video_is_v4l2_dev(cam);

	if(0 != ret)
	{
		return ret;
	}

	if(0 == fcntl(cam->fd, F_GETFD))
	{
		close(cam->fd);
	}

	cam->fd = open(cam->dev_name, O_RDWR | O_NONBLOCK, 0);

	if(0 < cam->fd && 0 == fcntl(cam->fd, F_GETFD))
	{
		return video_set_channel(cam);
	}
	return -EBADF;
}

static void video_release(struct camera_class **cam)
{
	uint32_t i = 0;

	if(NULL == cam || NULL == *cam)
	{
		return;
	}

	if(0 == video_stream_living(*cam))
	{
		video_stream_stop(*cam);
	}
	
	for(i = 0; (*cam)->n_buf > i; i++)
	{
		if(NULL != (*cam)->buf && MAP_FAILED != (*cam)->buf[i].start && NULL != (*cam)->buf[i].start)
		{
			munmap((*cam)->buf[i].start, (*cam)->buf[i].length);
		}
	}

	if(NULL != (*cam)->buf)
	{
		free((*cam)->buf);
	}

	if(0 == fcntl((*cam)->fd, F_GETFD))
	{
		if(0 != close((*cam)->fd))
		{
			ERR_DUMP("Close device");
		}

		INF_DUMP("Device was closed");
	}

	INF_DUMP("Object was free");

	free(*cam);
	*cam = NULL;
}

struct camera_class *video_init(char *dev_name, int channel, uint32_t width, uint32_t height, uint32_t format)
{
	struct camera_class *cam = NULL;
	if(NULL != dev_name && 0 <= channel && 4 > channel && 0 < width && 0 < height && 8000 > width && 8000 > height)
	{
		cam = (struct camera_class *)calloc(1, sizeof(*cam));
		if(NULL != cam)
		{
			strncpy(cam->dev_name, dev_name, DEVICE_NAME_SIZE);
			cam->width = width;
			cam->height = height;

			switch(format)
			{
				case V4L2_PIX_FMT_YUYV:
				case V4L2_PIX_FMT_UYVY:
				case V4L2_PIX_FMT_RGB32:
				case V4L2_PIX_FMT_JPEG:
				case V4L2_PIX_FMT_MJPEG:
					cam->pixel_format = format;
					break;
				default:
					errno = EINVAL;
					goto fail_release;
			}

			cam->fd = -1;
			cam->channel = channel;

			cam->n_buf = 0;
			cam->living = -1;
			cam->active_buf_index = -1;
	
			cam->buf = NULL;

			cam->video_open = video_open;
			cam->new_channel = video_set_new_channel;
			cam->show_cap = video_show_cap;
			cam->enum_fmt = video_enum_fmt;
			cam->set_fmt = video_set_fmt;
			cam->req_buf = video_req_buf;
			cam->alloc_buf = video_alloc_buf;
			cam->mmap_buf = video_mmap_buf;
			cam->init_buf = video_init_buf;
			cam->push_buf = video_push_buf;
			cam->pop_buf = video_pop_buf;
			cam->start_stream = video_stream_start;
			cam->stop_stream = video_stream_stop;
			cam->is_living = video_stream_living;
			cam->ignore_frame = video_ignore_frame;
			cam->get_frame = video_get_frame;
			cam->get_bufdup = video_get_bufdup;
			cam->free_bufdup = video_free_bufdup;
			cam->release = video_release;
		}
		else
		{
			errno = ENOMEM;
		}
	}
	else
	{
		errno = EINVAL;
	}
	return cam;

fail_release:
	free(cam);
	cam = NULL;
	return NULL;
}